package com.whz.gateway.filter;

import cn.hutool.core.lang.Tuple;
import com.beust.jcommander.internal.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.whz.gateway.common.GatewayMessageAccessor;
import com.whz.gateway.common.constants.FilterOrder;
import com.whz.gateway.common.constants.GatewayConstant;
import com.whz.gateway.common.exception.BadPathException;
import com.whz.gateway.common.util.AttributeUtils;
import com.whz.gateway.common.util.HeaderUtils;
import com.whz.gateway.common.util.PathMatcherUtils;
import com.whz.gateway.common.web.GatewayResponseBody;
import com.whz.gateway.configure.access.admin.AdminAccessProperties;
import com.whz.gateway.user.UserDetailInfo;
import com.whz.gateway.user.UserDetailWrapper;
import com.whz.generic.Constants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.RequestPath;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.net.URI;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 权限控制
 *
 * @author whz
 */
@Slf4j
@Component
public class AccessControlFilter extends AbstractGlobalFilter implements Ordered {

    @Resource
    private GatewaySwitcher gatewaySwitcher;

    @Resource
    private AdminAccessProperties adminAccessProperties;

    @Override
    protected boolean shouldFilter(ServerWebExchange exchange) {
        HeaderUtils.TokenInfo token = HeaderUtils.getTokenValue(exchange);
        // 权控开关
        boolean should =
                token != null && !StringUtils.isEmpty(token.getToken()) && !"null".equalsIgnoreCase(token.getToken());
        if (should) {
            log.info("current user login token: {}", token);
        } else {
            log.warn("current user has login ,but token is empty.");
        }
        return should;
    }

    @Override
    protected Mono<Void> apply(ServerWebExchange exchange, GatewayFilterChain chain) {
        HeaderUtils.TokenInfo token = HeaderUtils.getTokenValue(exchange);
        UserDetailInfo userDetails = validToken(token);

        if (userDetails == null) {
            log.warn("current token: [{}] is invalid.", token);
            return returnWithHttpStatus(exchange, HttpStatus.UNAUTHORIZED, GatewayMessageAccessor.getMessage(
                    "gateway.filter.AccessControlFilter.InvalidToken", token));
        }

        // 访问控制开关
        boolean requireAccessControl = gatewaySwitcher.isRequireAccessControl();
        log.info("Gateway Access Control Switch:{}", requireAccessControl);
        if (requireAccessControl && !canAccess(exchange, userDetails)) {
            RequestPath path = exchange.getRequest().getPath();
            log.warn("current token: [{}] Has No Permission for '{}'", token, path);
            return returnWithHttpStatus(exchange, HttpStatus.FORBIDDEN, GatewayMessageAccessor.getMessage(
                    "gateway.filter.AccessControlFilter.NoPermission", path.value()));
        }

        // 用户信息保存起来,留待后续处理业务,比如付费业务
        HeaderUtils.saveCurrentUserDetails(exchange, userDetails);

        // 添加必要的业务header后,继续下面的filter
        return chain.filter(HeaderUtils.addIfAbsent(exchange, buildUserHeader(userDetails)));
    }

    private static Mono<Void> returnWithHttpStatus(final ServerWebExchange exchange,
                                                   final HttpStatus status,
                                                   final String error) {
        return GatewayResponseBody.builder()
                .code(status.value())
                .message(status.name())
                .path(exchange.getRequest().getPath().toString())
                .method(exchange.getRequest().getMethodValue())
                .timestamp(System.currentTimeMillis())
                .error(error)
                .build()
                .sendToResponse(exchange.getResponse());
    }

    /**
     * valid token
     *
     * @param token current token
     * @return null if token invalid or user info
     */
    private static UserDetailInfo validToken(final HeaderUtils.TokenInfo token) {
        try {
            switch (token.getBiz()) {
                case API:
                    // API采用了虚拟用户名,代理权限结合
                case USER:
                    // 真实的用户
                default:
                    // 用的同一个接口
                    return UserDetailWrapper.getInstance().getUserDetail(token.getToken());
            }
        } catch (Exception exception) {
            log.error("when valid token error. token:" + token, exception);
            return null;
        }
    }


    private static HashMap<String, String> buildUserHeader(UserDetailInfo userDetails) {
        // TODO 根据业务保留透传的header
        HashMap<String, String> m = Maps.newHashMap();
        m.put(Constants.X_USER_ID, String.valueOf(userDetails.getUserId()));
        m.put(Constants.X_USER_NAME, userDetails.getUserName());
        return m;
    }

    private boolean canAccess(final ServerWebExchange exchange, final UserDetailInfo userDetails) {
        log.info("admin-user access properties:{}", adminAccessProperties);

        // protect-path pass
        if (isProtectedPath(exchange)) {
            log.info("protected path:{} NOT Required ACC", exchange.getRequest().getPath());
            return true;
        }

        // 查询用户权限列表
        List<UserDetailInfo.Authority> permissions = userDetails.getAuthority();
        if (CollectionUtils.isEmpty(permissions)) {
            log.warn("current user has not any permission,user detail:[{}].", userDetails);
            return false;
        }

        // 保存当前用户权限集合
        HeaderUtils.savePermissions(exchange, permissions);

        // 查询当前请求router
        Route route = AttributeUtils.getRouterAfterStrip(exchange);
        if (route != null) {

            // 构建权限map<appName,List<permission>>
            Map<String, List<String>> map = buildPermission(permissions);

            // 判断是否有此应用的权限
            if (!map.containsKey(route.getId())) {
                log.warn("current user detail: [{}] has not 'permissions' of service [{}].", userDetails,
                        route.getId());
                return false;
            }

            // 查询 path 权限集合
            Set<String> paths = getPaths(route.getId(), map);
            if (CollectionUtils.isEmpty(paths)) {
                log.warn("current user detail: [{}] has not 'path-permissions' of service [{}].", userDetails,
                        route.getId());
                return false;
            }

            // match 用户访问的path
            Tuple path = getPath(exchange, route);
            log.info("current access: serviceId:{} - path :{}.", route.getId(), path);
            return paths.stream().anyMatch(permission -> {
                // 处理权限字符串
                Tuple p = UserDetailInfo.Authority.resolvePermission(permission);
                if (p.size() > 1) {
                    return org.apache.commons.lang.StringUtils.equalsIgnoreCase(p.get(0), path.get(0)) && matchPathString(p.get(1), path.get(1));
                }
                boolean match = matchPath(p.get(0), path.get(1));
                if (log.isDebugEnabled()) {
                    log.debug("permission : {}, match-path : {}, result : {} ", permission, p, match);
                }
                return match;
            });
        }
        // 这种情况不太可能
        throw new BadPathException();
    }

    /**
     * 当前访问路径<method,url>
     */
    private static Tuple getPath(final ServerWebExchange exchange, final Route route) {
        String path;
        // strip prefix
        URI url = AttributeUtils.getUrlAfterStrip(exchange);
        if (url != null) {
            path = url.getPath();
        } else {
            // raw path
            path = exchange.getRequest().getPath().toString();
            // serviceId 开头
            if (path.startsWith(AntPathMatcher.DEFAULT_PATH_SEPARATOR + route.getId())) {
                path = AntPathMatcher.DEFAULT_PATH_SEPARATOR
                        + Arrays.stream(StringUtils.tokenizeToStringArray(path, AntPathMatcher.DEFAULT_PATH_SEPARATOR))
                        .skip(1)
                        .collect(Collectors.joining(AntPathMatcher.DEFAULT_PATH_SEPARATOR));
            }
        }
        return new Tuple(exchange.getRequest().getMethod().name(), StringUtils.trimAllWhitespace(path));
    }

    private static Set<String> getPaths(final String appName,
                                        final Map<String, List<String>> map) {
        return Sets.newConcurrentHashSet(map.get(appName));
    }

    /**
     * 查询用户权限
     *
     * @param permissions 权限列表
     * @return map<appid, list < permission>>
     */
    private static Map<String, List<String>> buildPermission(
            final List<UserDetailInfo.Authority> permissions) {
        Map<String, List<String>> temp = Maps.newHashMap();
        for (UserDetailInfo.Authority permission : permissions) {
            // gateway 这里只处理权限: (method)+url
            if (temp.containsKey(permission.getAppName())) {
                temp.get(permission.getAppName()).addAll(permission.getAuthrites());
            } else {
                temp.put(permission.getAppName(), Lists.newArrayList(permission.getAuthrites()));
            }
        }
        return temp;
    }

    /**
     * 受保护的路径只需要登录即可访问
     */
    private static boolean isProtectedPath(final ServerWebExchange exchange) {
        return PathMatcherUtils.matchs(exchange.getRequest().getPath().toString(),
                GatewayConstant.PROTECTED_LIST_PATHS);
    }

    @Override
    public int getOrder() {
        return FilterOrder.ACCESS_CONTROL_ORDER;
    }
}